/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
 * Description: jni实现层和使用penspeed的native调用
 * Author: Huawei Technologies Co.
 * Create: 2022-1-14
 */

#define SK_SUPPORT_DEPRECATED_CLIPOPS

#include <jni.h>
#include <string>
#include <vector>
#include <thread>
#include <queue>
#include <iostream>
#include <map>
#include <shared_mutex>

#include <SkPath.h>
#include <SkColor.h>
#include <SkRegion.h>

#include "PenSpeedInterface.h"
#include <android/binder_ibinder.h>
#include <android/binder_ibinder_jni.h>
#include <android/log.h>
#include <android/api-level.h>
#include <skia/core/SkPaint.h>
#include <skia/core/SkBitmap.h>
#include <skia/core/SkCanvas.h>
#include <dlfcn.h>

#include "ScopedTimeCostReporter.h"

constexpr const char *TAG = "PEN_SPEED_DEMO";
#ifdef DEBUG_LOG
#define LOGD(...) (void)__android_log_print(ANDROID_LOG_DEBUG, TAG, ##__VA_ARGS__)
#define LOGI(...) (void)__android_log_print(ANDROID_LOG_INFO, TAG, ##__VA_ARGS__)
#define LOGE(...) (void)__android_log_print(ANDROID_LOG_ERROR, TAG, ##__VA_ARGS__)
#define LOGW(...) (void)__android_log_print(ANDROID_LOG_WARN, TAG, ##__VA_ARGS__)
#else
#define LOGD(...)
#define LOGI(...)
#define LOGE(...)
#define LOGW(...)
#endif
#define LOGT(...) (void)__android_log_print(ANDROID_LOG_INFO, TAG, ##__VA_ARGS__)

using namespace hwpenspeed;
using namespace std::chrono_literals;

ScopedTimeCostReporter::CallBackType g_reportCostFunc = [](const char *name, std::chrono::microseconds time) {
    LOGT("%s cost: %llu us\n", name, time.count());
};

// 根据RGBA颜色值计算Skia颜色值
static SkColor MakeSkColorFromRgba(unsigned int uiColor)
{
    return (((uiColor & 0x000000FF) << 24) | (uiColor >> 8)); // rgba转换成argb，前三字节右移8位，最后一字节左移24位
}

#ifdef __cplusplus
extern "C" {
#endif
struct TouchPoint {
    int id;
    float x;
    float y;
};

// 是否使用实时拿到点的方案
bool g_getPointerInTime = true;

SkPath *pskPath_0;
int g_penArgbColor = 0xff5BD2F6;
float g_penWidth = 4.0f;
SkPath *pskPath_1;
FlushRect g_flushRect = {};

std::map<int, std::pair<bool, int>> g_drawingStateMap;
std::mutex g_mutex;
std::atomic<int> g_startTimesAfterClearTimer(0);

PenSpeedInterface *penSpeedInterface = nullptr;
std::shared_mutex g_interfaceMutex;

SkPaint g_paint;

SkBitmap *mpBitmap;

SkCanvas *mpCanvas;
SkCanvas *g_pCanvasSecond;

std::thread g_canvasSecondThread;
std::queue<std::function<void()>> g_canvasQueue;
std::mutex g_canvasQueueMutex;
std::condition_variable g_canvasCv;

const CanvasMap *canvasMap;

CanvasParam canvasParam;

static JavaVM *g_jvm = nullptr;
jobject g_proxyObj = nullptr;
static jclass g_javaClass = nullptr;
static jmethodID fastPenOnTouchMethodId = nullptr;

std::thread g_touchPointsReceiveThread;
std::atomic<bool> g_receiveThreadStop = { false };
std::queue<TouchPoint> g_queue;
std::mutex g_queueMutex;
std::condition_variable g_cv;

int g_viewLeft = 0;
int g_viewRight = 0;
int g_viewBottom = 0;
int g_viewTop = 0;
int g_viewHeight = 0;
int g_viewWidth = 0;

static void UpdateDirtyRect(const PointF &pt)
{
    const float doublePenWidth = g_penWidth * 2;

    if (g_flushRect.height == 0.f) {
        g_flushRect.pt = { pt.x - g_penWidth, pt.y - g_penWidth };
        g_flushRect.height = doublePenWidth;
        g_flushRect.width = doublePenWidth;
    } else {
        float ll = pt.x - g_penWidth;
        float tt = pt.y - g_penWidth;
        float rr = pt.x + doublePenWidth;
        float bb = pt.y + doublePenWidth;
        ll = std::min(ll, g_flushRect.pt.x);
        tt = std::min(tt, g_flushRect.pt.y);
        rr = std::max(g_flushRect.width + g_flushRect.pt.x, rr);
        bb = std::max(g_flushRect.height + g_flushRect.pt.y, bb);
        g_flushRect = { { ll, tt }, (rr - ll + 1), (bb - tt + 1) };
    }
}

JNIEnv *g_staticEnv = nullptr;
void OnTouchCallback(int pointerId, PointF pt)
{
    if (g_jvm == nullptr || fastPenOnTouchMethodId == nullptr) {
        return;
    }

    if (g_staticEnv) {
        g_staticEnv->CallVoidMethod(g_proxyObj, fastPenOnTouchMethodId, pointerId, pt.x, pt.y);
        return;
    }

    if (JNI_OK != g_jvm->GetEnv((void **)&g_staticEnv, JNI_VERSION_1_6)) {
        g_staticEnv = nullptr;
        (void)g_jvm->AttachCurrentThread((JNIEnv **)&g_staticEnv, nullptr);
    }

    if (g_staticEnv) {
        g_staticEnv->CallVoidMethod(g_proxyObj, fastPenOnTouchMethodId, pointerId, pt.x, pt.y);
    }
}

static void ClipInView(int &left, int &top, int &right, int &bottom)
{
    left = std::max(left, 0);
    top = std::max(top, 0);
    right = std::min(right, g_viewWidth);
    bottom = std::min(bottom, g_viewHeight);
}

void PointProc(int pointerId, PointF pt)
{
    std::lock_guard<std::mutex> lock(g_mutex);
    if (g_drawingStateMap.count(pointerId) == 0) {
        return;
    }
    auto it = g_drawingStateMap[pointerId];
    int pathIdx = it.second;

    UpdateDirtyRect(pt);

    OnTouchCallback(pointerId, pt);
    float pointX = pt.x;
    float pointY = pt.y;

    SkPath *pskPath = pathIdx == 0 ? pskPath_0 : pskPath_1;
    if (pskPath == nullptr) {
        return;
    }
    // 计算剪切域
    int cnt = pskPath->countPoints();
    LOGI("PointerCallbackFunc pointerId=%d, %f, %f cnt %d", pointerId, pt.x, pt.y, cnt);
    SkPoint lastPoint = pskPath->getPoint(cnt - 1);
    float lastX = lastPoint.fX;
    float lastY = lastPoint.fY;

    SkRegion clipRegion;
    int left = (lastX > pointX ? pointX : lastX) - g_penWidth;
    int top = (lastY > pointY ? pointY : lastY) - g_penWidth;
    int right = (lastX > pointX ? lastX : pointX) + g_penWidth;
    int bottom = (lastY > pointY ? lastY : pointY) + g_penWidth;
    ClipInView(left, top, right, bottom);
    clipRegion.setRect(left, top, right, bottom);

    if (mpCanvas != nullptr) {
        mpCanvas->clipRegion(clipRegion, SkClipOp::kReplace_deprecated);
        pskPath->lineTo(SkPoint::Make(pointX, pointY));
        mpCanvas->drawPath(*pskPath, g_paint);
        if (cnt > 30) { // 30个点重写一条笔迹
            pskPath->reset();
            pskPath->moveTo(SkPoint::Make(pointX, pointY));
        }
        if (!clipRegion.isEmpty()) {
            LOGI("PEN_SPEED. kotori: left=%d, top=%d, right=%d, bottom=%d end", left, top, right, bottom);
            FlushRect fr = { { (float)left, (float)top }, (float)(right - left + 1), (float)(bottom - top + 1) };
            penSpeedInterface->FlushCanvas(fr);
        }
    }
}

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
{
    JNIEnv *env = nullptr;
    jint ret = -1;

    g_jvm = vm;

    if ((ret = vm->GetEnv((void **)&env, JNI_VERSION_1_6)) != JNI_OK) {
        LOGE("JNI_OnLoad GetEnv ret %d", ret);
        return ret;
    }
    if (env == nullptr) {
        return ret;
    }
    ret = JNI_VERSION_1_6;

    g_touchPointsReceiveThread = std::thread([]() {
        while (!g_receiveThreadStop) {
            std::unique_lock<std::mutex> lock(g_queueMutex);
            if (!g_queue.empty()) {
                TouchPoint tp = g_queue.front();
                g_queue.pop();
                lock.unlock();
                PointProc(tp.id, { tp.x, tp.y });
            } else {
                g_cv.wait(lock, []() { return !g_queue.empty(); });
            }
        }
        g_receiveThreadStop.store(false);
    });

    return ret;
}

// 使用实时拿到点的方案，在这里处理你的逻辑
void PointerCallbackFunc(int pointerId, PointF pt)
{
    std::unique_lock<std::mutex> lock(g_queueMutex);
    TouchPoint tp = { pointerId, pt.x, pt.y };
    g_queue.emplace(tp);
    g_cv.notify_one();
}

static void *ChangeJavaBinder(JNIEnv *env, void *javaBinder)
{
    using FN_AIBinder_fromJavaBinder = void *(*)(JNIEnv *, void *);
    static void *ndkBinderHandler = dlopen("libbinder_ndk.so", RTLD_LAZY);
    if (ndkBinderHandler != nullptr) {
        static FN_AIBinder_fromJavaBinder func_AIBinder_fromJavaBinder =
            (FN_AIBinder_fromJavaBinder)dlsym(ndkBinderHandler, "AIBinder_fromJavaBinder");
        return func_AIBinder_fromJavaBinder(env, javaBinder);
    }
    return nullptr;
}


// 初始化PenSpeedInterface对象，创建PenSpeed
JNIEXPORT int JNICALL Java_com_device_cp_penspeeddemo_PenSpeedProxy_createNativePenSpeedInterface(JNIEnv *env,
    jobject o, jobject srvBinder)
{
    g_proxyObj = env->NewGlobalRef(o);
    if (g_javaClass == nullptr) {
        jclass localClassRef = env->GetObjectClass(g_proxyObj);
        g_javaClass = static_cast<jclass>((env)->NewGlobalRef(localClassRef));
        fastPenOnTouchMethodId = env->GetMethodID(g_javaClass, "fastPenOnTouch", "(IFF)V");
    }

    void *pServBinder = ChangeJavaBinder(env, reinterpret_cast<jobject>(srvBinder));
    {
        std::unique_lock<std::shared_mutex> shareLock(g_interfaceMutex);
        if (penSpeedInterface != nullptr) {
            delete penSpeedInterface;
        }
        penSpeedInterface = PenSpeedInterface::Create((void *)pServBinder);
        int level = android_get_device_api_level();
        if (penSpeedInterface == nullptr) {
            return -1;
        }
        LOGT("kotori [android] level = %d version=%s", level, penSpeedInterface->GetSdkVersion());
    }
    return 0;
}

static void SetSkPaint()
{
    g_paint.setStrokeCap(SkPaint::kRound_Cap);
    g_paint.setStrokeJoin(SkPaint::kRound_Join);
    g_paint.setColor(g_penArgbColor);
    g_paint.setAntiAlias(true);
    g_paint.setStrokeWidth(g_penWidth);
    g_paint.setStyle(SkPaint::kStroke_Style);
    g_paint.setBlendMode(SkBlendMode::kSrcOver);
}

// 创建画布
JNIEXPORT void JNICALL Java_com_device_cp_penspeeddemo_PenSpeedProxy_nativeFbCreateCanvas(JNIEnv *env,
    jobject /* this */, jint posX, jint posY, jint width, jint height)
{
    g_viewLeft = posX;
    g_viewTop = posY;
    g_viewRight = g_viewLeft + width;
    g_viewBottom = g_viewTop + height;
    g_viewWidth = width;
    g_viewHeight = height;
    canvasParam.posX = posX;
    canvasParam.posY = posY;
    canvasParam.width = width;
    canvasParam.height = height;
    {
        std::shared_lock<std::shared_mutex> shareLock(g_interfaceMutex);
        if (penSpeedInterface != nullptr) {
            canvasMap = penSpeedInterface->CreateCanvas(canvasParam);
        }
    }

    if (canvasMap == nullptr) {
        LOGE("Java_com_device_cp_penspeeddemo_PenSpeedProxy_nativeFbCreateCanvas: canvasMap = %p", canvasMap);
        return;
    }
    // 设置画笔
    SetSkPaint();
    // 使用实时拿到点的方案
    if (g_getPointerInTime) {
        std::shared_lock<std::shared_mutex> shareLock(g_interfaceMutex);
        if (penSpeedInterface != nullptr) {
            penSpeedInterface->SetCallback(PointerCallbackFunc);
        }
    }

    void *pFbAddr = (void *)(canvasMap->mapAddress);
    LOGI("kotori: fbAddr = %p", pFbAddr);

    // 位图
    mpBitmap = new SkBitmap();
    size_t stride = (size_t)(canvasMap->rowStride);
    mpBitmap->installPixels(
        SkImageInfo::Make(canvasParam.width, canvasParam.height, kARGB_4444_SkColorType, kOpaque_SkAlphaType), pFbAddr,
        stride);
    if (mpBitmap->getPixels() == nullptr) {
        delete mpBitmap;
        mpBitmap = nullptr;
        return;
    }
    LOGI("kotori: mpBitmap->getPixels() = %p", mpBitmap->getPixels());
    // 画布
    mpCanvas = new SkCanvas(*mpBitmap);
    mpCanvas->resetMatrix();
    mpCanvas->translate(0, 0);

    return;
}

// 删除画布
JNIEXPORT void JNICALL Java_com_device_cp_penspeeddemo_PenSpeedProxy_nativeFbDeleteCanvas(JNIEnv *env,
    jobject /* this */)
{
    // 提笔不清空，需要删除画布时清空
    if (mpCanvas != nullptr) {
        SkRegion clipRegion;
        clipRegion.setEmpty();
        clipRegion.setRect(0, 0, canvasParam.width, canvasParam.height);
        mpCanvas->clipRegion(clipRegion, SkClipOp::kIntersect);
        mpCanvas->clear(0x00000000);

        if (mpBitmap != nullptr) {
            mpBitmap->eraseColor(0x00000000);
            delete mpBitmap;
            mpBitmap = nullptr;
        }
        FlushRect fullView = { { (float)g_viewLeft, (float)g_viewTop }, (float)g_viewWidth, (float)g_viewHeight };
        {
            std::shared_lock<std::shared_mutex> shareLock(g_interfaceMutex);
            if (penSpeedInterface != nullptr) {
                penSpeedInterface->FlushCanvas(fullView);
            }
        }
        delete mpCanvas;
        mpCanvas = nullptr;
    }
    {
        std::shared_lock<std::shared_mutex> shareLock(g_interfaceMutex);
        if (penSpeedInterface != nullptr) {
            penSpeedInterface->DeleteCanvas();
        }
    }
    return;
}

// 落笔
JNIEXPORT void JNICALL Java_com_device_cp_penspeeddemo_PenSpeedProxy_nativeFbStart(JNIEnv *env, jobject /* this */,
    jint pointerId, jfloat pointX, jfloat pointY)
{
    ScopedTimeCostReporter report("nativeFbStart", g_reportCostFunc);
    LOGI("kotori: start pen enter pointerId %d pointX:%f pointY:%f", pointerId, pointX, pointY);
    {
        std::lock_guard<std::mutex> lock(g_mutex);
        if (g_drawingStateMap.size() >= 2) {
            return;
        }

        if (g_drawingStateMap.count(pointerId) == 1) {
            return;
        }

        int pathIdx = g_drawingStateMap.size();
        if (pathIdx == 1) {
            for (const auto &it : g_drawingStateMap) {
                pathIdx = it.second.second == 0 ? 1 : 0;
            }
        }
        g_startTimesAfterClearTimer.fetch_add(1);
        g_drawingStateMap.emplace(std::make_pair(pointerId, std::make_pair(true, pathIdx)));

        if (pathIdx == 0 && pskPath_0 == nullptr) {
            pskPath_0 = new SkPath();
        } else if (pathIdx == 1 && pskPath_1 == nullptr) {
            pskPath_1 = new SkPath();
        }
        auto p = pathIdx == 0 ? pskPath_0 : pskPath_1;
        p->reset();
        p->moveTo(SkPoint::Make(pointX, pointY));
        PointF pt = { pointX, pointY };
        PointerCallbackFunc(pointerId, (pt));
        {
            std::shared_lock<std::shared_mutex> shareLock(g_interfaceMutex);
            if (penSpeedInterface != nullptr) {
                penSpeedInterface->StartWrite(pointerId, (pt));
            }
        }
    }
    LOGI("kotori: start pen end");
    return;
}

static int GetPathIdxPyPointerId(int pointerId)
{
    if (g_drawingStateMap.empty()) {
        return -1;
    }
    auto it = g_drawingStateMap.find(pointerId);
    if (it == g_drawingStateMap.end()) {
        return -1;
    }
    int pathIdx = it->second.second;
    return pathIdx;
}

// 获取点并进行线的渲染
JNIEXPORT void JNICALL Java_com_device_cp_penspeeddemo_PenSpeedProxy_nativeFbGetPt(JNIEnv *, jobject /* this */,
    jint pointerId)
{
    // 取点
    point_vector<PointF> points;
    {
        std::lock_guard<std::mutex> lock(g_mutex);
        int pathIdx = GetPathIdxPyPointerId(pointerId);
        if (pathIdx == -1) {
            return;
        }
        SkPath *pskPath = pathIdx == 0 ? pskPath_0 : pskPath_1;
        {
            std::shared_lock<std::shared_mutex> shareLock(g_interfaceMutex);
            if (penSpeedInterface != nullptr) {
                penSpeedInterface->GetPointer(pointerId, points);
            }
        }
        for (int j = 0; j < points.size(); j++) {
            PointerCallbackFunc(pointerId, points[j]);
        }
    }
    return;
}

void ClearCanvas()
{
    ScopedTimeCostReporter report("ClearCanvas", g_reportCostFunc);
    SkRegion clipRegion;
    clipRegion.setRect(g_flushRect.pt.x - g_viewLeft, g_flushRect.pt.y - g_viewTop,
        g_flushRect.pt.x + g_flushRect.width, g_flushRect.pt.y + g_flushRect.height);
    mpCanvas->clipRegion(clipRegion, SkClipOp::kReplace_deprecated);
    {
        ScopedTimeCostReporter report("mpCanvas->clear", g_reportCostFunc);
        mpCanvas->clear(0x00000000);
    }
    {
        ScopedTimeCostReporter report("FlushCanvas(g_flushRect);", g_reportCostFunc);
        {
            std::shared_lock<std::shared_mutex> shareLock(g_interfaceMutex);
            if (penSpeedInterface != nullptr) {
                penSpeedInterface->FlushCanvas(g_flushRect);
            }
        }
    }
    g_flushRect = {};
}

static void TimerFunc()
{
    auto startTime = std::chrono::high_resolution_clock::now();
    while (g_startTimesAfterClearTimer.load() == 0) {
        constexpr auto timeOut = 500ms;
        std::this_thread::sleep_for(10ms);
        auto elps = std::chrono::high_resolution_clock::now() - startTime;
        std::lock_guard<std::mutex> lock(g_mutex);
        if (g_startTimesAfterClearTimer.load() == 0) {
            if (elps > timeOut) {
                ClearCanvas();
                return;
            }
        } else {
            return;
        }
    }
}

// 抬笔
JNIEXPORT void JNICALL Java_com_device_cp_penspeeddemo_PenSpeedProxy_nativeFbStop(JNIEnv *, jobject /* this */,
    jint pointerId)
{
    ScopedTimeCostReporter report("nativeFbStop", g_reportCostFunc);
    LOGI("kotori: stop pen enter");

    int touchCnt = 0;
    {
        std::lock_guard<std::mutex> lock(g_mutex);
        if (g_drawingStateMap.empty()) {
            return;
        }
        auto it = g_drawingStateMap.find(pointerId);
        if (it == g_drawingStateMap.end()) {
            return;
        }
        int pathIdx = it->second.second;
        g_drawingStateMap.erase(it);
        if (pskPath_1 && pathIdx == 1) {
            pskPath_1->reset();
        }
        if (pskPath_0 && pathIdx == 0) {
            pskPath_0->reset();
        }
        {
            std::shared_lock<std::shared_mutex> shareLock(g_interfaceMutex);
            if (penSpeedInterface != nullptr) {
                penSpeedInterface->StopWrite(pointerId);
            }
        }
        touchCnt = g_drawingStateMap.size();
        if (mpCanvas != nullptr && touchCnt == 0) {
            g_startTimesAfterClearTimer.store(0);
            std::thread(TimerFunc).detach();
        }
    }
    LOGI("kotori: stop pen end");
}

// 清理对象
JNIEXPORT void JNICALL Java_com_device_cp_penspeeddemo_PenSpeedProxy_nativeDelete(JNIEnv *env, jobject thiz)
{
    if (pskPath_0 != nullptr) {
        delete pskPath_0;
        pskPath_0 = nullptr;
    }
    if (pskPath_1 != nullptr) {
        delete pskPath_1;
        pskPath_1 = nullptr;
    }
    if (mpCanvas != nullptr) {
        delete mpCanvas;
        mpCanvas = nullptr;
    }

    {
        std::unique_lock<std::shared_mutex> lock(g_interfaceMutex);
        if (penSpeedInterface != nullptr) {
            delete penSpeedInterface;
            penSpeedInterface = nullptr;
        }
    }

    if (env->IsSameObject(g_proxyObj, thiz)) {
        LOGE("Java_com_device_cp_penspeeddemo_PenSpeedProxy_nativeDelete object not same");
    }

    env->DeleteGlobalRef(g_proxyObj);
    env->DeleteGlobalRef(g_javaClass);
    g_proxyObj = nullptr;
    g_javaClass = nullptr;
    return;
}

extern "C" JNIEXPORT void JNICALL Java_com_device_cp_penspeeddemo_PenSpeedProxy_nativeSetFastPenColorWidth(JNIEnv *env,
    jobject thiz, jint argb_color, jfloat pen_width)
{
    g_penArgbColor = argb_color;
    g_penWidth = pen_width;
}

extern "C" JNIEXPORT void JNICALL Java_com_device_cp_penspeeddemo_PenSpeedProxy_nativeSetGetPointerWay(JNIEnv *env,
    jobject thiz, jboolean use_get_way)
{
    g_getPointerInTime = !use_get_way;
}
#ifdef __cplusplus
}
#endif
